/*
 * Copyright (c) 2005-2018. YingQi Anime Technology Co. Ltd. All rights reserved.
 * FileName: DateUtils.java
 * Author:   LL
 * Date:     2018年3月21日 下午4:32:51
 * Description: //模块目的、功能描述
 * History: //修改记录
 * <author>      <time>      <version>    <desc>
 * 修改人姓名             修改时间            版本号                  描述
 */
package com.sfang.common.util.date;

import com.sfang.common.base.collection.CustomKeyValuePair;
import com.sfang.common.util.string.StringUtils;

import java.text.SimpleDateFormat;
import java.time.*;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalQuery;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

/**
 * 时间相关工具<br>
 * 〈功能详细描述〉
 *
 * @author LL
 * @see [相关类/方法]（可选）
 * @since [产品/模块版本] （可选）
 */
public class DateUtilsForBeijing {

    public static final String PATTERN_Mmillisecond = "yyyy-MM-dd HH:mm:ss.SSS";
    public static final String PATTERN_Hour = "yyyyMMddHH";

    public static final String PATTERN_Day = "yyyyMMdd";
    public static final String PATTERN_Day_Strigula = "yyyy-MM-dd";
    public static final String PATTERN_Month = "yyyyMM";
    public static final String PATTERN_Second = "yyyy-MM-dd HH:mm:ss";
    public static final String PATTERN_OrderNo = "yyMMddHHmm";


    private static SimpleDateFormat FORMAT_Asian() {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        format.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
        return format;
    }

    private static SimpleDateFormat FORMAT_Asian_Second() {
        SimpleDateFormat format = new SimpleDateFormat(PATTERN_Second);
        format.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
        return format;
    }


    public static Date VALIDATOR_START_DATE() {
        return parse("1970-1-1 00:00:00.000", PATTERN_Mmillisecond);
    }

    public static Date VALIDATOR_END_DATE() {
        return parse("2030-10-1 00:00:00.000", PATTERN_Mmillisecond);
    }

    public static Date minusDays(Date date, long days) {
        LocalDateTime ldt = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
        return Date.from(ldt.minusDays(days).atZone(ZoneId.systemDefault()).toInstant());
    }

    public static Date minusHours(Date date, long hours) {
        LocalDateTime ldt = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
        return Date.from(ldt.minusHours(hours).atZone(ZoneId.systemDefault()).toInstant());
    }

    public static Date minusMonths(Date date, int months) {
        Calendar rightNow = Calendar.getInstance();
        rightNow.setTime(date);
        rightNow.add(Calendar.MONTH, -months);
        return rightNow.getTime();
    }

    public static Date plusDays(Date date, long daysToAdd) {
        LocalDateTime ldt = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
        return Date.from(ldt.plusDays(daysToAdd).atZone(ZoneId.systemDefault()).toInstant());
    }

    public static Date plusHour(Date date, long hoursToAdd) {
        LocalDateTime ldt = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
        return Date.from(ldt.plusHours(hoursToAdd).atZone(ZoneId.systemDefault()).toInstant());
    }

    public static Date plusMonths(Date date, long monthsToAdd) {
        LocalDateTime ldt = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
        return Date.from(ldt.plusMonths(monthsToAdd).atZone(ZoneId.systemDefault()).toInstant());
    }

    public static Date plusMins(Date date, long minsToAdd) {
        LocalDateTime ldt = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
        return Date.from(ldt.plusMinutes(minsToAdd).atZone(ZoneId.systemDefault()).toInstant());
    }

    public static Date plusSeconds(Date date, long minsToAdd) {
        LocalDateTime ldt = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
        return Date.from(ldt.plusSeconds(minsToAdd).atZone(ZoneId.systemDefault()).toInstant());
    }

    /**
     * 计算两个日期之间相差的天数
     *
     * @param date1
     * @param date2
     * @return
     */
    public static int daysBetween(Date date1, Date date2) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(date1);
        long time1 = cal.getTimeInMillis();
        cal.setTime(date2);
        long time2 = cal.getTimeInMillis();
        long between_days = (time2 - time1) / (1000 * 3600 * 24);

        return Integer.parseInt(String.valueOf(between_days));
    }

    public static int hoursBetween(Date date1, Date date2) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(date1);
        long time1 = cal.getTimeInMillis();
        cal.setTime(date2);
        long time2 = cal.getTimeInMillis();
        long between_days = (time2 - time1) / (1000 * 3600);

        return Integer.parseInt(String.valueOf(between_days));
    }

    public static int minutesBetween(Date date1, Date date2) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(date1);
        long time1 = cal.getTimeInMillis();
        cal.setTime(date2);
        long time2 = cal.getTimeInMillis();
        long between_days = (time2 - time1) / (1000 * 60);

        return Integer.parseInt(String.valueOf(between_days));
    }

    public static int secondsBetween(Date date1, Date date2) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(date1);
        long time1 = cal.getTimeInMillis();
        cal.setTime(date2);
        long time2 = cal.getTimeInMillis();
        long between_days = (time2 - time1) / 1000;

        return Integer.parseInt(String.valueOf(between_days));
    }

    public static Date firstDayOfMonth() {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");

        // 获取前月的第一天
        Calendar cale = Calendar.getInstance();
        cale.add(Calendar.MONTH, 0);
        cale.set(Calendar.DAY_OF_MONTH, 1);
        String firstday = format.format(cale.getTime());

        try {
            return format.parse(firstday);
        } catch (Exception e) {
            return new Date();
        }
    }

    public static Date lastDayOfMonth() {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        Calendar cale = Calendar.getInstance();
        cale.add(Calendar.MONTH, 1);
        cale.set(Calendar.DAY_OF_MONTH, 0);
        String lastday = format.format(cale.getTime());

        try {
            return DateUtils.plusDays(format.parse(lastday), 1);
        } catch (Exception e) {
            return new Date();
        }
    }


    public static Date parseFromMmilliseconds(String mmilliseconds) {
        long lt = new Long(mmilliseconds);
        return formatAsianDate(new Date(lt));
    }

    public static Date parse(String source) {
        Date date = null;
        try {
            date = FORMAT_Asian().parse(source);
        } catch (Exception e) {
            try {
                date = FORMAT_Asian_Second().parse(source);
            } catch (Exception err) {
                date = DateUtilsForBeijing.newDate();
            }
        }

        if (date != null && date.after(DateUtilsForBeijing.newDate()))
            date = DateUtilsForBeijing.newDate();

        return date;
    }

    public static Date parseWithoutBoundary(String source) {
        Date date = null;
        try {
            date = FORMAT_Asian().parse(source);
        } catch (Exception e) {
            try {
                date = FORMAT_Asian_Second().parse(source);
            } catch (Exception err) {
                date = DateUtilsForBeijing.newDate();
            }
        }

        return date;
    }

    public static Date parse(String source, String pattern) {
        SimpleDateFormat bjsDateFormat = new SimpleDateFormat(pattern);
        bjsDateFormat.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));//北京

        Date ts = null;//创建Date对象
        try {
            ts = bjsDateFormat.parse(source);//转换为Date类型 这里要抛一个异常
        } catch (Exception e) {
            ts = DateUtilsForBeijing.newDate();
        }
        return ts;
    }

    public static String formatAsianString(Date date) {
        if (date == null)
            date = new Date();

        return FORMAT_Asian().format(date);
    }

    public static String formatAsianString(Date date, String pattern) {
        SimpleDateFormat bjsDateFormat = new SimpleDateFormat(pattern);
        bjsDateFormat.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));//北京
        return bjsDateFormat.format(date);
    }

    public static Date formatAsianDate(Date date) {
        return parseDate(FORMAT_Asian().format(date));
    }

    public static Date newDate() {
        return parseDate(FORMAT_Asian().format(new Date()));
    }

    public static Date newDate(long mmilliseconds) {
        return parseDate(FORMAT_Asian().format(new Date(mmilliseconds)));
    }

    private static Date parseDate(String time) {

        Date date = null;
        try {
            date = new SimpleDateFormat(PATTERN_Mmillisecond).parse(time);
        } catch (Exception e) {
            try {
                date = new SimpleDateFormat(PATTERN_Second).parse(time);
            } catch (Exception e1) {
                date = new Date();
            }
        }

        return date;
    }

    //获取当天最小时间
    public static Date getDateMin() {
        LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.
                        ofEpochMilli(DateUtilsForBeijing.newDate().getTime()),
                ZoneId.systemDefault()).with(LocalTime.MIN);
        ZoneId zoneId = ZoneId.systemDefault();
        ZonedDateTime zdt = localDateTime.atZone(zoneId);
        Date date = Date.from(zdt.toInstant());
        return date;
    }


    // 获得某天最大时间 2017-10-15 23:59:59
    public static Date getEndOfDay(Date date) {
        LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()), ZoneId.systemDefault());
        ;
        LocalDateTime endOfDay = localDateTime.with(LocalTime.MAX);
        return Date.from(endOfDay.atZone(ZoneId.systemDefault()).toInstant());
    }

    // 获得某天最小时间 2017-10-15 00:00:00
    public static Date getStartOfDay(Date date) {
        LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()), ZoneId.systemDefault());
        LocalDateTime startOfDay = localDateTime.with(LocalTime.MIN);
        return Date.from(startOfDay.atZone(ZoneId.systemDefault()).toInstant());
    }

    public static Long microMillisecond() {
        return System.nanoTime() * 100;
    }

    public static String parseDateReturnString(String source) {
        Date date = null;

        if (StringUtils.isEmpty(source))
            date = DateUtilsForBeijing.newDate();

        try {
            date = FORMAT_Asian().parse(source);
        } catch (Exception e) {
            try {
                date = FORMAT_Asian_Second().parse(source);
            } catch (Exception err) {
                date = DateUtilsForBeijing.newDate();
            }
        }

        if (date != null && date.after(DateUtilsForBeijing.newDate()))
            date = DateUtilsForBeijing.newDate();

        String rlt = formatAsianString(date, PATTERN_Mmillisecond);
        return rlt;
    }

    public static String takeValidate(Date... dates) {
        for (Date date : dates)
            if (date.after(VALIDATOR_START_DATE()) && date.before(VALIDATOR_END_DATE()))
                try {
                    return formatAsianString(date, PATTERN_Mmillisecond);
                } catch (Exception e) {
                    try {
                        return formatAsianString(date, PATTERN_Second);
                    } catch (Exception err) {
                        return formatAsianString(newDate(), PATTERN_Mmillisecond);
                    }
                }

        return formatAsianString(newDate(), PATTERN_Mmillisecond);
    }

    public static String takeValidate(String... dates) {

        for (String dateVal : dates) {
            Date date = parseDate(dateVal);
            if (date.after(VALIDATOR_START_DATE()) && date.before(VALIDATOR_END_DATE()))
                try {
                    return formatAsianString(date, PATTERN_Mmillisecond);
                } catch (Exception e) {
                    try {
                        return formatAsianString(date, PATTERN_Second);
                    } catch (Exception err) {
                        return formatAsianString(newDate(), PATTERN_Mmillisecond);
                    }
                }
        }

        return formatAsianString(newDate(), PATTERN_Mmillisecond);
    }

    public static String hourFormat(Date date) {
        return formatAsianString(date, PATTERN_Hour);
    }

    public static String dayFormat(Date date) {
        return formatAsianString(date, PATTERN_Day);
    }

    public static String orderNoFormat() {
        return formatAsianString(newDate(), PATTERN_OrderNo);
    }

    public static String minuteFormat(Date date) {
        return formatAsianString(date, PATTERN_OrderNo);
    }

    public static String monthFormat(Date date) {
        return formatAsianString(date, PATTERN_Month);
    }

    public static CustomKeyValuePair<Date, Date> getDateByWeek(Integer year, Integer week) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        Calendar cal = Calendar.getInstance();
        cal.setFirstDayOfWeek(Calendar.MONDAY);
        cal.set(Calendar.YEAR, year);
        cal.set(Calendar.WEEK_OF_YEAR, week);
        cal.set(Calendar.DAY_OF_WEEK, cal.getFirstDayOfWeek());
        Date start = cal.getTime();
        cal.add(Calendar.DAY_OF_WEEK, 7);
        Date end = cal.getTime();
        return CustomKeyValuePair.create(start, end);
    }

    public static Integer getWeek(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setFirstDayOfWeek(Calendar.MONDAY);
        calendar.setTime(date);
        return calendar.get(Calendar.WEEK_OF_YEAR);
    }

    public static Integer getQuarter(Date date) {
        TemporalQuery<Integer> quarterOfYearQuery = new QuarterOfYearQuery();
        LocalDate localDate = LocalDate.parse(DateUtilsForBeijing.formatAsianString(date, PATTERN_Day_Strigula));
        return localDate.query(quarterOfYearQuery);

    }

    public static class QuarterOfYearQuery implements TemporalQuery {
        @Override
        public Integer queryFrom(TemporalAccessor temporal) {
            LocalDate now = LocalDate.from(temporal);

            if (now.isBefore(now.with(Month.APRIL).withDayOfMonth(1))) {
                return 1;
            } else if (now.isBefore(now.with(Month.JULY).withDayOfMonth(1))) {
                return 2;
            } else if (now.isBefore(now.with(Month.NOVEMBER).withDayOfMonth(1))) {
                return 3;
            } else {
                return 4;
            }
        }

    }

    public static CustomKeyValuePair<Date, Date> getDayRange(Date date) {
        date = parse(dayFormat(date), "yyyyMMdd");
        return CustomKeyValuePair.create(date, plusHour(date, 23));
    }

    public static CustomKeyValuePair<Date, Date> getWeekRange(Date date) {

        Calendar cal = Calendar.getInstance();
        cal.setFirstDayOfWeek(Calendar.MONDAY);
        cal.setTime(date);

        int dayWeek = cal.get(Calendar.DAY_OF_WEEK);
        if (dayWeek == 1) {
            dayWeek = 7;
        } else {
            dayWeek -= 1;
        }

        // 计算本周开始的时间
        cal.add(Calendar.DAY_OF_MONTH, 1 - dayWeek);
        Date startDate = parse(dayFormat(cal.getTime()), "yyyyMMdd"),
                endDate = plusDays(startDate, 6);

        return CustomKeyValuePair.create(startDate, plusHour(endDate, 23));
    }

    public static CustomKeyValuePair<Date, Date> getMonthRange(Date date) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        // 设置为1号,当前日期既为本月第一天
        cal.set(Calendar.DAY_OF_MONTH, 1);
        Date startDate = parse(dayFormat(cal.getTime()), "yyyyMMdd"),
                endDate = minusHours(plusMonths(startDate, 1), 1);
        return CustomKeyValuePair.create(startDate, endDate);
    }

    public static CustomKeyValuePair<Date, Date> getYearRange(Date date) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        // 设置为1号,当前日期既为本月第一天
        cal.set(Calendar.DAY_OF_YEAR, 1);
        Date startDate = parse(dayFormat(cal.getTime()), "yyyyMMdd"),
                endDate = minusHours(plusMonths(startDate,12 ), 1);
        return CustomKeyValuePair.create(startDate, endDate);
    }
}
